请你解释一下:什么是浅拷贝(shallow copy)?什么是深拷贝(deep copy)?它们的核心区别是什么?
浅拷贝(shallow copy)和深拷贝(deep copy)的核心区别在于:是否递归复制对象的引用类型属性。
1、浅拷贝
浅拷贝只会复制对象的第一层属性,如果属性值是引用类型(对象/数组),复制的是引用地址,而不是值本身。
因此:
2、深拷贝
深拷贝会递归复制对象的所有层级,生成一个完全独立的新对象。
因此修改任意层级的数据都不会影响原对象
3、核心区别总结
本质区别是:
✅ Standard Answer (English)
The key difference between shallow copy and deep copy is whether nested reference types are recursively copied or not.
1、Shallow Copy
A shallow copy only copies the first level properties of an object. If a property is a reference type (like an object or array), it copies the reference address instead of the actual value.
So:
2、Deep Copy
A deep copy recursively copies all levels of an object, creating a fully independent clone.
So:
3、Key Difference
请你说一下:在 JavaScript 中,常见的浅拷贝方式有哪些?(至少说出 3 种) Can you list common ways to perform shallow copy in JavaScript? (at least 3 methods)
常见浅拷贝方式(JavaScript)
xxxxxxxxxxconst obj2 = Object.assign({}, obj1);中文: Object.assign 会创建一个新对象,并把第一层属性复制进去,因此是浅拷贝。
English: Object.assign creates a new object and copies first-level properties into it, making it a shallow copy.
xxxxxxxxxxconst obj2 = { obj1 };中文: 展开运算符会复制对象的第一层属性,但嵌套对象仍然是引用。
English: The spread operator copies only the first-level properties, while nested objects are still referenced.
xxxxxxxxxxconst arr2 = arr.slice();const arr3 = arr.concat();const arr4 = [arr];中文: 这些方法都会生成一个新的数组,但内部元素如果是引用类型,仍然共享。
English: These methods create a new array, but nested reference types are still shared.
为什么 JSON.parse(JSON.stringify(obj)) 可以实现深拷贝?它有什么缺陷? Why can JSON.parse(JSON.stringify(obj)) achieve deep copy? What are its limitations?
问题一:
中文解释
这个方法的本质是:
先把对象转成 JSON 字符串,再重新解析成一个新对象
xxxxxxxxxxconst copy = JSON.parse(JSON.stringify(obj));过程是:
JSON.stringify(obj)
👉 把 JS 对象“序列化”为字符串(会丢失引用关系)JSON.parse(...)
👉 再把字符串解析成一个全新的对象✔ 所以它天然“断开引用”,实现类似深拷贝效果
English explanation
The idea is:
Convert the object into a JSON string, then parse it back into a new object.
Steps:
JSON.stringify(obj) serializes the object into a string, removing references.JSON.parse(...) creates a completely new object from that string.✔ This breaks reference links, which makes it behave like a deep copy.
问题二:
中文答案,这个比较多,看一下理解即可:
xxxxxxxxxxconst obj = { fn: function () {}}👉 会被直接忽略
xxxxxxxxxx{ a: undefined, b: Symbol("id")}👉 会丢失
xxxxxxxxxxconst obj = {}obj.self = obj👉 会直接报错:
Converting circular structure to JSON
比如:
英文回答:
undefined and Symbol
你能手写一个“完整支持对象 + 数组 + 循环引用”的深拷贝吗?思路是什么?
Can you implement a deep clone function that supports objects, arrays, and circular references? What is your approach?
先讲思路:
实现深拷贝的核心是三件事:
1️⃣ 判断类型
区分:
2️⃣ 区分对象和数组
xxxxxxxxxxArray.isArray(obj)3️⃣ 解决循环引用(重点)
用一个结构记录“已经拷贝过的对象”:
👉 Map 或 WeakMap
作用:防止对象无限递归
Core idea
To implement deep clone, we need to:
Key technique: WeakMap
We use WeakMap to store already cloned objects:
代码:
xfunction deepClone(obj, map = new WeakMap()) { // 1. 基本类型直接返回 if (obj === null || typeof obj !== "object") { return obj; } // 2. 解决循环引用 if (map.has(obj)) { return map.get(obj); } // 3. 判断数组还是对象 const result = Array.isArray(obj) ? [] : {}; // 4. 记录引用关系 map.set(obj, result); // 5. 遍历属性 for (let key in obj) { if (obj.hasOwnProperty(key)) { result[key] = deepClone(obj[key], map); } } return result;}为什么需要map来解决循环引用?
xxxxxxxxxxconst a = { name: "对象A" };const b = { name: "对象B" };// 制造循环引用:A 引用 B,B 又引用 Aa.friend = b;b.friend = a;// 如果尝试 deepClone(a) 且不带 map:// 1. 克隆 a -> 发现 a 有属性 friend 指向 b// 2. 克隆 b -> 发现 b 有属性 friend 指向 a// 3. 克隆 a -> 再次发现 a 有属性 friend 指向 b// 4. 克隆 b -> ... (无穷无尽)那如果加上了map,那map做了什么呢?
第一步:克隆对象 a
map.has(a)。表是空的,返回 false。const resultA = {}。map.set(a, resultA)。此时 map 记录了:“如果你再遇到原对象 a,请直接使用新对象 resultA。”a.friend(即对象 b)。第二步:克隆对象 b(在 a 的递归中)
map.has(b)。表里只有 a,没有 b,返回 false。const resultB = {}。map.set(b, resultB)。此时 map 记录了:{ a -> resultA, b -> resultB }。b.friend(即对象 a)。第三步:再次遇到对象 a(致命时刻)
检查表:执行 map.has(a)。命中! 返回 true。
直接返回:执行 return map.get(a)。
deepClone(a) 的递归,而是直接把之前创建的 resultA 给吐出来。链路闭合:resultB.friend 被赋值为 resultA。
第四步:回溯
b 克隆完成,返回 resultB。resultA.friend 被赋值为 resultB。resultA。你能说一下 ES6 你最常用的几个新特性有哪些吗?(至少说 3 个) What are some ES6 features you commonly use? (at least 3)
中文
我常用的 ES6 特性有:
let / const:用于块级作用域,避免变量提升带来的问题English
Some ES6 features I frequently use include:
let / const for block scoping and avoiding hoisting issuesthisimport / export) for better code organization
箭头函数和普通函数的 this 有什么区别?
What is the difference between this in arrow functions and regular functions?
中文高分表达
箭头函数和普通函数最大的区别在于 this 的绑定方式不同。 普通函数的 this 是在调用时决定的,取决于调用方式; 而箭头函数没有自己的 this,它的 this 是在定义时就确定的,会继承外层作用域的 this。
English 口语版
The main difference is how
thisis determined. For regular functions,thisis decided at call time depending on how the function is invoked. For arrow functions,thisis lexically bound, meaning it is determined at definition time and inherited from the outer scope.
加强理解:
xxxxxxxxxxconst user = { name: 'Alice', greet: function() { // Standard function inside a timer setTimeout(function() { console.log('Standard:', this.name); }, 100); // Arrow function inside a timer setTimeout(() => { console.log('Arrow:', this.name); }, 100); }};user.greet();
因为箭头函数的this是在定义是确定的,所以第二个输出是Alice。
箭头函数为什么不能作为构造函数? Why can't arrow functions be used as constructors?
中文
箭头函数不能作为构造函数,主要有两个原因:
1️⃣ 没有 prototype
xxxxxxxxxxconst fn = () => {};console.log(fn.prototype); // undefined👉 构造函数必须有 prototype,用于实例继承
2️⃣ 没有自己的 this
箭头函数的 this 是继承外层作用域的:
👉 但构造函数需要一个新的 this(指向实例)
❗ 直接结果
xxxxxxxxxxconst Person = () => {};new Person(); // 报错👉 会报错:
Person is not a constructor
✅ 一句话总结(面试必杀)
箭头函数不能作为构造函数,因为它没有 prototype,并且没有自己的 this
✅ English Version
Arrow functions cannot be used as constructors for two main reasons:
prototypexxxxxxxxxxconst fn = () => {};console.log(fn.prototype); // undefinedConstructors must have a prototype for instances to inherit from.
thisArrow functions do not have their own this;
they inherit this from the outer scope.
👉 But constructors require a new this bound to the instance.
Result
xxxxxxxxxxconst Person = () => {};new Person(); // ErrorPerson is not a constructor
"Arrow functions cannot be used as constructors primarily because they lack two things: their own this binding and a prototype property."
"箭头函数不能作为构造函数,主要是因为它缺少两样东西:它没有自己的 this 绑定,也没有 prototype 属性。"
"When you use the new keyword, JS tries to create a new object and bind it to the function's this. But arrow functions inherit this from the surrounding scope (lexical this). They don't have a 'blank' this that new can point to."
"当你使用 new 关键字时,JS 会尝试创建一个新对象并把它绑定到函数的 this 上。但箭头函数是从外层作用域继承 this 的(词法 this)。它没有一个可以被 new 指向的‘空白’ this。"
"Every constructor needs a prototype so that the new instance can inherit methods. Arrow functions don't have a .prototype property at all, so there’s no blueprint for the new object to follow."
"每个构造函数都需要一个 prototype 属性,以便新实例可以继承方法。箭头函数完全没有 .prototype 属性,所以新对象也就没有可以遵循的‘蓝图’。"
"If you try to call an arrow function with new, JS will throw a TypeError immediately. It’s not just that it won't work—the engine is literally designed to prevent it."
"如果你尝试对箭头函数使用 new,JS 会立刻抛出 TypeError。这不只是能不能用的问题,而是引擎从设计上就禁止了这种行为。"
箭头函数有没有 arguments?如果没有,怎么获取参数?
Do arrow functions have arguments? If not, how can you access arguments?
✅ 一句话总结(面试用)
箭头函数没有自己的 arguments,它会继承外层作用域的 arguments,通常用 ...args 来获取参数
✅ English Version
👉 No, arrow functions do not have their own arguments object. They inherit arguments from the outer scope.
那么如何获取参数呢?使用...args来获取:
How to access arguments?
👉 Use rest parameters:
xxxxxxxxxxconst fn = (args) => { console.log(args);};Arrow functions don’t have their own
argumentsobject.They inheritargumentsfrom the outer scope. use rest parameters instead.
案例说明:箭头函数没有自己的 arguments,它会继承外层作用域的 arguments。如果想拿到传递给箭头函数的参数,需要使用rest parameters,也就是...args来获取。
xxxxxxxxxxfunction outer() { const fn = () => { console.log(arguments); }; fn(1, 2, 3);}outer(4, 5, 6);
解构赋值有哪些常见用法?以及它有哪些坑点? What are common use cases of destructuring, and what are its pitfalls?
English
Common use cases:
坑点:
解构赋值我在使用中主要注意几个点: 第一,默认值只在属性是 undefined 的时候才生效,如果是 null 是不会触发默认值的。 第二,如果对 null 或 undefined 进行解构会直接报错,所以一般会加一个兜底,比如用
|| {}。 第三,嵌套解构的时候,如果中间某一层是 undefined,也会报错,需要给默认值。 还有一个是,数组解构是按顺序来的,对象解构是按 key 来的,这一点要区分。
✅ English(面试版)
When using destructuring, there are a few important pitfalls: First, default values only work when the value is
undefined, notnull. Second, destructuringnullorundefinedwill throw an error, so we usually provide a fallback like|| {}. Third, nested destructuring can fail if an intermediate property isundefined, so we need default values there. Also, array destructuring is position-based, while object destructuring is key-based.
深入理解:
1、常见用法
1️⃣ 对象解构
xxxxxxxxxxconst { name, age } = user;2️⃣ 数组解构
xxxxxxxxxxconst [a, b] = arr;3️⃣ 函数参数解构
xxxxxxxxxxfunction fn({ name, age }) {}2、坑点
🧠 坑 1:默认值只在 undefined 时生效
xxxxxxxxxxconst { a = 10 } = { a: null };console.log(a); // null ❗
👉 不会使用默认值
✅ 结论
默认值只在属性为 undefined 时才生效
English
Default values only apply when the value is undefined, not null.
🧠 坑 2:解构 null / undefined 会直接报错
xxxxxxxxxxconst { a } = null; // ❌ 报错
✅ 解决方案
xxxxxxxxxxconst { a } = obj || {};
English
Destructuring null or undefined will throw an error.
Solution:
xxxxxxxxxxconst { a } = obj || {};
🧠 坑 3:嵌套解构容易报错
xxxxxxxxxxconst { a: { b } } = obj;
👉 如果 a 是 undefined → 直接崩
✅ 安全写法
xxxxxxxxxxconst { a: { b } = {} } = obj;
🧠 坑 4:变量重命名
xxxxxxxxxxconst { name: username } = user;
👉 不是赋值,而是重命名
🧠 坑 5:数组解构是按位置,不是按名字
xxxxxxxxxxconst [a, b] = [1, 2];
👉 顺序非常重要
扩展运算符(...)和 rest 参数有什么区别? What is the difference between the spread operator and rest parameters?
中文
你可以这样说:
扩展运算符和 rest 参数本质上是同一个语法
...,但使用场景不同。spread 是用来“展开”的,比如把数组或对象拆开,用在赋值或者函数调用的时候。
rest 是用来“收集”的,比如在函数参数中,把多个参数收集成一个数组。
一个是往外展开,一个是往里收集。
English
Spread and rest use the same syntax
..., but they serve different purposes.Spread is used to expand values, like spreading an array or object.
Rest is used to collect values, usually in function parameters.
One expands, the other collects.
new 关键字在 JavaScript 中做了什么?
What does the new keyword do in JavaScript?
中文
new 关键字在 JavaScript 中主要做了四件事。
第一,它会创建一个新的空对象。 第二,它会把这个新对象的原型指向构造函数的 prototype。 第三,它会把构造函数内部的 this 绑定到这个新对象上。 第四,如果构造函数没有显式返回对象,就默认返回这个新对象。
✅ English version
The
newkeyword in JavaScript performs four steps:First, it creates a new empty object. Second, it sets the object's prototype to the constructor's prototype. Third, it binds
thisinside the constructor to the new object. Fourth, it returns the object unless the constructor explicitly returns another object.
方便记忆:
new = 创建对象 + 绑定 this + 继承 prototype + 返回对象
Can you implement the new operator manually?
你能手写 new 的实现吗?
xxxxxxxxxxfunction myNew(Constructor, args) { // 1. 创建一个空对象 const obj = {}; // 2. 让这个对象的原型指向构造函数的 prototype obj.__proto__ = Constructor.prototype; // 3. 让构造函数的 this 指向这个对象,并执行 const result = Constructor.apply(obj, args); // 4. 如果构造函数返回的是对象,就返回它;否则返回 obj return result instanceof Object ? result : obj;}
"A Pure Function is a function that returns the same output given the same input and has no side effects." "纯函数是指给定相同的输入,永远返回相同输出,且没有副作用的函数。"
"In React, pure functions make the UI predictable. It allows React to skip re-renders using memo or useMemo because it knows the output only changes when the input changes."
"在 React 中,纯函数让 UI 变得可预测。它让 React 能够通过 memo 或 useMemo 跳过不必要的渲染,因为 React 知道只有当输入变化时,输出才会变化。"
一个纯函数必须满足两个条件:
Date.now() 或全局变量)。为什么 React 执着于此? 因为 React 需要“可预测性”。 如果你的组件函数是纯的,React 就可以放心地缓存它的渲染结果。如果你的函数像写法 A 那样偷偷改了外面的变量,React 就无法追踪到底发生了什么变化。
高阶函数(Higher-Order Functions, HOF)
这是 React 逻辑复用的核心。
定义: 一个函数如果能接收另一个函数作为参数,或者返回一个函数,它就是高阶函数。
JS 例子:map, filter, reduce。
React 例子:
withAuth(Profile),接收一个组件,返回一个带权限检查的新组件。memoize 函数,其实就是一个经典的高阶函数。
"The biggest difference is that for loops are Imperative, while map() is Declarative. In React, we prefer telling the UI what to render rather than how to step through the DOM."
"最大的区别在于 for 循环是命令式的,而 map() 是声明式的。在 React 中,我们更倾向于告诉 UI 要渲染什么,而不是一步步告诉它如何去操作 DOM。"
A. 不可变性 (Immutability)
"
map()creates a new array instead of modifying the original one. This aligns perfectly with React’s 'Immutability' principle, making state tracking much easier.""
map()会创建一个新数组,而不是修改原数组。这完美契合 React 的‘不可变性’原则,让状态追踪变得简单得多。"
B. 表达式 vs 语句 (Expression vs Statement)
"In JSX, we can only embed expressions. Since
map()returns a value, it can be used directly inside our templates. Aforloop is a statement, which would require extra code to push items into an array.""在 JSX 中,我们只能嵌入表达式。因为
map()有返回值,它可以直接写在模板里。而for循环是语句,需要额外的代码(比如先定义一个空数组再 push)才能工作。"
C. 链式调用 (Chainability)
"
map()allows for easy chaining. We can filter the data and then map it in one go, which leads to cleaner and more readable code.""
map()支持链式调用。我们可以先filter过滤数据,然后紧接着map渲染,这让代码更加简洁易读。"